# akshare_finance/fetcher.py
import time
import logging
import random
from pathlib import Path
from typing import Optional
import pandas as pd
import akshare as ak

logger = logging.getLogger(__name__)


class FinancialDataFetcher:
    """Akshare 调用封装，带重试和简单缓存"""

    def __init__(
        self,
        max_retries: int = 3,
        retry_delay: float = 1.0,
        enable_cache: bool = False,
        cache_dir: str = ".cache",
        cache_ttl_seconds: int = 24 * 3600,
    ) -> None:
        self.max_retries = max_retries
        self.retry_delay = retry_delay
        self.enable_cache = enable_cache
        self.cache_dir = Path(cache_dir)
        self.cache_ttl_seconds = cache_ttl_seconds
        self._cache: dict[str, pd.DataFrame] = {}

        if enable_cache:
            self.cache_dir.mkdir(parents=True, exist_ok=True)

    def _get_cache_path(self, key: str) -> Path:
        return self.cache_dir / f"{key}.csv"

    def _load_cache(self, key: str) -> Optional[pd.DataFrame]:
        if not self.enable_cache:
            return None

        # 内存缓存
        if key in self._cache:
            return self._cache[key]

        # 磁盘缓存
        cache_file = self._get_cache_path(key)
        if not cache_file.exists():
            return None

        try:
            # 检查是否过期
            if (time.time() - cache_file.stat().st_mtime) > self.cache_ttl_seconds:
                return None

            df = pd.read_csv(cache_file)
            self._cache[key] = df
            return df
        except Exception:
            return None

    def _save_cache(self, key: str, df: pd.DataFrame) -> None:
        if not self.enable_cache:
            return

        self._cache[key] = df
        try:
            df.to_csv(self._get_cache_path(key), index=False)
        except Exception:
            pass

    def _safe_call(self, func, symbol: str, **kwargs) -> pd.DataFrame:
        """带重试的安全调用"""
        cache_key = f"{func.__name__}_{symbol}"
        if kwargs:
            cache_key += "_" + "_".join(f"{k}:{v}" for k, v in sorted(kwargs.items()))

        # 尝试从缓存加载
        cached = self._load_cache(cache_key)
        if cached is not None:
            return cached

        last_error = None
        # 重试逻辑
        for i in range(self.max_retries):
            try:
                # 增加延迟，避免请求过快
                if i > 0:
                    sleep_time = self.retry_delay * (2**i) * random.uniform(1.0, 2.0)
                    time.sleep(sleep_time)
                else:
                    # 第一次调用也稍微延迟，避免并发过多
                    time.sleep(random.uniform(0.1, 0.5))

                df = func(symbol=symbol, **kwargs)

                # 验证返回数据
                if df is None:
                    raise ValueError("API返回None")

                if not isinstance(df, pd.DataFrame):
                    df = pd.DataFrame(df)

                # 检查是否为空数据
                if df.empty:
                    logger.debug(f"{func.__name__}({symbol}) 返回空数据")

                self._save_cache(cache_key, df)
                return df

            except Exception as e:
                last_error = e
                error_msg = str(e)

                # 不同错误类型的处理
                if "Expecting value" in error_msg:
                    # JSON解析错误，可能是API限流或临时问题
                    if i < self.max_retries - 1:
                        continue
                elif "timeout" in error_msg.lower():
                    # 超时错误
                    if i < self.max_retries - 1:
                        continue
                elif "404" in error_msg or "not found" in error_msg.lower():
                    # 数据不存在，不需要重试
                    break

                if i == self.max_retries - 1:
                    logger.warning(
                        f"调用 {func.__name__}({symbol}) 最终失败: {error_msg}"
                    )

        return pd.DataFrame()

    def get_financial_abstract(self, symbol: str, indicator: str = "按报告期") -> pd.DataFrame:
        def _fetch(symbol: str, indicator: str):
            # 转换股票代码格式为东方财富接口要求的格式
            em_symbol = self._convert_to_em_symbol(symbol)
            # 使用东方财富接口，提供更完整的财务指标
            return ak.stock_financial_analysis_indicator_em(symbol=em_symbol, indicator=indicator)
        
        return self._safe_call(_fetch, symbol, indicator=indicator)
    
    def _convert_to_em_symbol(self, symbol: str) -> str:
        """转换股票代码为东方财富格式（包含交易所后缀）"""
        # 如果已经包含后缀，直接返回
        if '.' in symbol:
            return symbol
            
        # 根据股票代码前缀判断交易所
        if symbol.startswith(('000', '001', '002', '003')):
            return f"{symbol}.SZ"  # 深圳证券交易所
        elif symbol.startswith(('600', '601', '603', '605', '688')):
            return f"{symbol}.SH"  # 上海证券交易所
        else:
            # 默认尝试深圳交易所
            return f"{symbol}.SZ"

    def get_main_business(self, symbol: str) -> pd.DataFrame:
        return self._safe_call(ak.stock_zyjs_ths, symbol)

    def get_financial_report_sina(self, symbol: str, which: str) -> pd.DataFrame:
        def _fetch(symbol: str, which: str):
            return ak.stock_financial_report_sina(stock=symbol, symbol=which)

        return self._safe_call(_fetch, symbol, which=which)
